// PART OF THE MACHINE SIMULATION. DO NOT CHANGE.

package nachos.security;

import nachos.machine.*;

import java.io.File;
import java.security.Permission;
import java.io.FilePermission;
import java.util.PropertyPermission;
import java.net.NetPermission;
import java.awt.AWTPermission;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;

/**
 * Protects the environment from malicious Nachos code.
 */
public class NachosSecurityManager extends SecurityManager
{
    /**
     * Allocate a new Nachos security manager.
     *
     * @param    testDirectory    the directory usable by the stub file system.
     */
    public NachosSecurityManager(File testDirectory)
    {
        this.testDirectory = testDirectory;

        fullySecure = Config.getBoolean("NachosSecurityManager.fullySecure");
    }

    /**
     * Return a privilege object for this security manager. This security
     * manager must not be the active security manager.
     *
     * @return a privilege object for this security manager.
     */
    public Privilege getPrivilege()
    {
        Lib.assertTrue(this != System.getSecurityManager());

        return new PrivilegeProvider();
    }

    /**
     * Install this security manager.
     */
    public void enable()
    {
        Lib.assertTrue(this != System.getSecurityManager());

        doPrivileged(new Runnable()
        {
            public void run()
            {
                System.setSecurityManager(NachosSecurityManager.this);
            }
        });
    }

    private class PrivilegeProvider extends Privilege
    {
        public void doPrivileged(Runnable action)
        {
            NachosSecurityManager.this.doPrivileged(action);
        }

        public Object doPrivileged(PrivilegedAction action)
        {
            return NachosSecurityManager.this.doPrivileged(action);
        }

        public Object doPrivileged(PrivilegedExceptionAction action)
                throws PrivilegedActionException
        {
            return NachosSecurityManager.this.doPrivileged(action);
        }

        public void exit(int exitStatus)
        {
            invokeExitNotificationHandlers();
            NachosSecurityManager.this.exit(exitStatus);
        }
    }

    private void enablePrivilege()
    {
        if (privilegeCount == 0)
        {
            Lib.assertTrue(privileged == null);
            privileged = Thread.currentThread();
            privilegeCount++;
        }
        else
        {
            Lib.assertTrue(privileged == Thread.currentThread());
            privilegeCount++;
        }
    }

    private void rethrow(Throwable e)
    {
        disablePrivilege();

        if (e instanceof RuntimeException)
            throw (RuntimeException) e;
        else if (e instanceof Error)
            throw (Error) e;
        else
            Lib.assertNotReached();
    }

    private void disablePrivilege()
    {
        Lib.assertTrue(privileged != null && privilegeCount > 0);
        privilegeCount--;
        if (privilegeCount == 0)
            privileged = null;
    }

    private void forcePrivilege()
    {
        privileged = Thread.currentThread();
        privilegeCount = 1;
    }

    private void exit(int exitStatus)
    {
        forcePrivilege();
        System.exit(exitStatus);
    }

    private boolean isPrivileged()
    {
        // the autograder does not allow non-Nachos threads to be created, so..
        if (!TCB.isNachosThread())
            return true;

        return (privileged == Thread.currentThread());
    }

    private void doPrivileged(final Runnable action)
    {
        doPrivileged(new PrivilegedAction()
        {
            public Object run()
            {
                action.run();
                return null;
            }
        });
    }

    private Object doPrivileged(PrivilegedAction action)
    {
        Object result = null;
        enablePrivilege();
        try
        {
            result = action.run();
        } catch (Throwable e)
        {
            rethrow(e);
        }
        disablePrivilege();
        return result;
    }

    private Object doPrivileged(PrivilegedExceptionAction action)
            throws PrivilegedActionException
    {
        Object result = null;
        enablePrivilege();
        try
        {
            result = action.run();
        } catch (Exception e)
        {
            throw new PrivilegedActionException(e);
        } catch (Throwable e)
        {
            rethrow(e);
        }
        disablePrivilege();
        return result;
    }

    private void no()
    {
        throw new SecurityException();
    }

    private void no(Permission perm)
    {
        System.err.println("\n\nLacked permission: " + perm);
        throw new SecurityException();
    }

    /**
     * Check the specified permission. Some operations are permissible while
     * not grading. These operations are regulated here.
     *
     * @param    perm    the permission to check.
     */
    public void checkPermission(Permission perm)
    {
        String name = perm.getName();

        // some permissions are strictly forbidden
        if (perm instanceof RuntimePermission)
        {
            // no creating class loaders
            if (name.equals("createClassLoader"))
                no(perm);
        }

        // allow the AWT mess when not grading
        if (!fullySecure)
        {
            if (perm instanceof NetPermission)
            {
                // might be needed to load awt stuff
                if (name.equals("specifyStreamHandler"))
                    return;
            }

            if (perm instanceof RuntimePermission)
            {
                // might need to load libawt
                if (name.startsWith("loadLibrary."))
                {
                    String lib = name.substring("loadLibrary.".length());
                    if (lib.equals("awt"))
                    {
                        Lib.debug(dbgSecurity, "\tdynamically linking " + lib);
                        return;
                    }
                }
            }

            if (perm instanceof AWTPermission)
            {
                // permit AWT stuff
                if (name.equals("accessEventQueue"))
                    return;
            }
        }

        // some are always allowed
        if (perm instanceof PropertyPermission)
        {
            // allowed to read properties
            if (perm.getActions().equals("read"))
                return;
        }

        // some require some more checking
        if (perm instanceof FilePermission)
        {
            if (perm.getActions().equals("read"))
            {
                // the test directory can only be read with privilege
                if (isPrivileged())
                    return;

                enablePrivilege();

                // not allowed to read test directory directly w/out privilege
                try
                {
                    File f = new File(name);
                    if (f.isFile())
                    {
                        File p = f.getParentFile();
                        if (p != null)
                        {
                            if (p.equals(testDirectory))
                                no(perm);
                        }
                    }
                } catch (Throwable e)
                {
                    rethrow(e);
                }

                disablePrivilege();
                return;
            }
            else if (perm.getActions().equals("write") ||
                    perm.getActions().equals("delete"))
            {
                // only allowed to write test diretory, and only with privilege
                verifyPrivilege();

                try
                {
                    File f = new File(name);
                    if (f.isFile())
                    {
                        File p = f.getParentFile();
                        if (p != null && p.equals(testDirectory))
                            return;
                    }
                } catch (Throwable e)
                {
                    no(perm);
                }
            }
            else if (perm.getActions().equals("execute"))
            {
                // only allowed to execute with privilege, and if there's a net
                verifyPrivilege();

                if (Machine.networkLink() == null)
                    no(perm);
            }
            else
            {
                no(perm);
            }
        }

        // default to requiring privilege
        verifyPrivilege(perm);
    }

    /**
     * Called by the <tt>java.lang.Thread</tt> constructor to determine a
     * thread group for a child thread of the current thread. The caller must
     * be privileged in order to successfully create the thread.
     *
     * @return a thread group for the new thread, or <tt>null</tt> to use the
     * current	thread's thread group.
     */
    public ThreadGroup getThreadGroup()
    {
        verifyPrivilege();
        return null;
    }

    /**
     * Verify that the caller is privileged.
     */
    public void verifyPrivilege()
    {
        if (!isPrivileged())
            no();
    }

    /**
     * Verify that the caller is privileged, so as to check the specified
     * permission.
     *
     * @param    perm    the permission being checked.
     */
    public void verifyPrivilege(Permission perm)
    {
        if (!isPrivileged())
            no(perm);
    }

    private File testDirectory;
    private boolean fullySecure;

    private Thread privileged = null;
    private int privilegeCount = 0;

    private static final char dbgSecurity = 'S';
}
